﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Unity.Errors.types;
using Unity.Logic.Expressions.statements;

namespace Unity.Logic.Expressions.expression
{
    public class TLogicalExpression : ILogic
    {
        #region ctors
        public TLogicalExpression()
        {
            _ScopedLogical = null;
        }
        public TLogicalExpression(TLogicalExpression logic)
        {
            _ScopedLogical = logic;
        }
        #endregion

        #region private fields
        private bool _LogicEnqueued;
        private bool _QueueConstructionEnded;
        private Queue<ILogic> _Logics;
        private Queue<ERelationType> _Relations;
        private ILogic[] _LogicsArray;
        private ERelationType[] _RelationsArray;
        private TLogicalExpression _ScopedLogical;
        #endregion

        #region methods
        public void BeginEnqueue()
        {
            _Relations = new Queue<ERelationType>();
            _Logics = new Queue<ILogic>();
            _LogicEnqueued = false;
            _QueueConstructionEnded = false;
        }
        public void EnqueueLogic(ILogic c)
        {
            SErrorManager.CustomError()
                     .Assert(_LogicEnqueued == false
                     , new TErrorInfo(GetType(), "AddComponent")
                     , "Components must be added first or added after relation.");

            _Logics.Enqueue(c);
            _LogicEnqueued = true;

        }
        public void EnqueueRelation(ERelationType r)
        {
            SErrorManager.CustomError()
                     .Assert(_LogicEnqueued == true
                     , new TErrorInfo(GetType(), "AddComponent")
                     , "Relation must be added after component.");
            _Relations.Enqueue(r);
            _LogicEnqueued = false;
        }
        public void EndEnqueue()
        {
            SErrorManager.CustomError()
                .Assert(_Logics.Count == _Relations.Count + 1
                , new TErrorInfo(GetType(), "DequeueConclusion")
                , "Conclusion can not be evaluated. Inappropriate component and relation count\n"
                + "(components:{0}, relations:{1}) component count must be 1 over relation count.", _Logics.Count, _Relations.Count);
            _QueueConstructionEnded = true;
            _LogicsArray = new ILogic[_Logics.Count];
            _RelationsArray = new ERelationType[_Relations.Count]; 
            _Logics.CopyTo(_LogicsArray, 0);
            _Relations.CopyTo(_RelationsArray, 0);
        }
        private bool OnOrder()
        {
            return _Logics.Count != 0;
        }
        private ILogic Dequeue()
        {
            SErrorManager.CustomError()
               .Assert(_QueueConstructionEnded != true
               , new TErrorInfo(GetType(), "Dequeue")
               , "Queue construction not complete. Please use EndEnqueue().");
            return _Logics.Dequeue();
        }
        private ILogic EvaluateLogic()
        {
            ILogic eval = null;
            ILogic lesserLogic = null;
            if (_ScopedLogical == null)
            {
                lesserLogic = _Logics.Dequeue();
                while (OnOrder())
                {
                    lesserLogic =
                        lesserLogic.EvaluateConclusionWith(_Logics.Dequeue(), _Relations.Dequeue());
                }
                eval = lesserLogic;
            }
            else
            {
                eval = _ScopedLogical.EvaluateLogic();
            }
            RestoreQueues();
            return eval;
        }

        private void RestoreQueues()
        {
            _Logics = new Queue<ILogic>(_LogicsArray);
            _Relations = new Queue<ERelationType>(_Relations);
        }
        #endregion

        #region overriden methods
        public override string ToString()
        {
            SErrorManager.CustomError()
                .Assert(_QueueConstructionEnded == true
                , new TErrorInfo(GetType(), "ToString")
                , "Queue construction not ended. Use EndEnqueue() after enqueing.");

            StringBuilder sb = new StringBuilder();
            if (_LogicsArray.Length == 1)
            {
                sb.AppendFormat("({0})", _LogicsArray[0].Text);
                return sb.ToString();
            }
            sb.Append("(");
            int i = 0, j = 0;
            while (true)
            {
                if (i == _LogicsArray.Length)
                    break;
                sb.AppendFormat("{0} {1} {2}"
                    , _LogicsArray[i].Text
                    , _RelationsArray[j].ToString()
                    , _LogicsArray[i + 1].Text);
                i += 2;
                j++;
            }
            sb.Append(")");
            return sb.ToString();
        }
        #endregion

        #region ILogic Members
        public TConclusion Conclusion
        {
            get
            {
                return EvaluateLogic().Conclusion;
            }
        }
        public ILogic EvaluateConclusionWith(ILogic logic, ERelationType relation)
        {
            switch (relation)
            {
                case ERelationType.NOT:
                    logic.Conclusion.Result = !logic.Conclusion.Result;
                    return logic;
                case ERelationType.AND:
                    logic.Conclusion.Result = this.Conclusion.Result && logic.Conclusion.Result;
                    return logic;
                case ERelationType.OR:
                    logic.Conclusion.Result = this.Conclusion.Result || logic.Conclusion.Result;
                    return logic;
                case ERelationType.EQV:
                    logic.Conclusion.Result = this.Conclusion.Result == logic.Conclusion.Result;
                    return logic;
                case ERelationType.NEQV:
                    logic.Conclusion.Result = this.Conclusion.Result != logic.Conclusion.Result;
                    return logic;
                case ERelationType.NA:
                    SErrorManager.Exception()
                        .Throw(new InvalidOperationException()
                        , new TErrorInfo(GetType(), "EvaluateConclusionWith")
                        , "ERelationType can not be NA. It must be assigned.");
                    break;
            }
            return logic;
        }
        public string Text
        {
            get { return ToString(); }
        }
        #endregion
    }
}
